# Copyright (c) 2022, yuxinyong and contributors
# For license information, please see license.txt

import copy
import frappe
from frappe import _
from zelin_am.utils import *
from frappe.utils import flt, add_days
import json


def execute(filters=None):
	columns = get_columns(filters)
	header_fields = [c.get('fieldname') for c in columns[:12]]
	data = get_data(filters, header_fields)
	return columns, data

def get_data(filters={}, header_fields = []):
	company = filters.get('company')
	fully_availability_priority = filters.get('fully_availability_priority')
	only_actual_stock = filters.get('only_actual_stock')
	warehouses =  frappe.get_all('Warehouse', filters={'company': company, 'mrp_exclude':0}, pluck='name')
	data = get_open_wo_gi(warehouses, filters = filters, need_header_info = True)
	wip_warehouse = {row.wip_warehouse for row in data}
	warehouses = [w for w in warehouses if w not in wip_warehouse]
	item_stock_qty_map = get_item_code_onhand_map(company,warehouses)
	if data: data = sorted(data, key=lambda k: (k['due_date']))
	item_code_list = list({d.item_code for d in data})
	if not only_actual_stock:
		open_po_list = get_open_po(warehouses, item_code_list = item_code_list,
			only_expected_delivery_date =bool(filters.get('include_confirmed_delivery')))
		open_po_list = sorted(open_po_list, key=lambda k: (k['item_code'], k['due_date']))	 
		item_po_qty_map = {}
		for row in open_po_list:
			po_qty_list = item_po_qty_map.setdefault(row.item_code, [])
			po_qty_list.append(row)
	last_idx = len(data) - 1	
	#按顺序分派库存及采购订单数量
	original_item_po_qty_map, original_item_stock_qty_map = {},{}
	for (idx,d) in enumerate(data):
		d.qty *= -1	#original function return qty as negative
		work_order, item_code, order_qty, required_qty, due_date = d.ref_docname, d.item_code, d.order_qty, d.qty, d.due_date
		d.allotted_stock_qty, d.allotted_po_qty, d.future_po_qty = 0, 0, 0
		if (not idx) or work_order != data[idx-1].ref_docname:
			cur_work_order_idx = idx 
			d.availability_status = _('Full Available')
			last_arrival_date = ''
			min_output_qty = d.open_qty
			d.available_percent = 100
			partial_available = False
			not_available = False
			can_fulfill = True
			fulfill_date = None
			if not only_actual_stock and fully_availability_priority:
				original_item_po_qty_map = copy.deepcopy(item_po_qty_map)
				original_item_stock_qty_map = copy.deepcopy(item_stock_qty_map)

		item_stock_data = item_stock_qty_map.get(item_code)

		if item_stock_data:
			available_qty = item_stock_data['onhand']
			allotted_qty = required_qty if available_qty >= required_qty else available_qty
			required_qty -= allotted_qty
			item_stock_data['onhand'] -= allotted_qty
			d.available_stock_qty = available_qty
			d.allotted_stock_qty = allotted_qty
		if not only_actual_stock and required_qty > 0:
			po_qty_list = item_po_qty_map.get(item_code,[])
			allotted_qty = 0
			for row in po_qty_list:
				if not d.arrival_date:
					d.arrival_qty, d.arrival_date,d.purchase_order, d.supplier = (
						row.qty,row.due_date,row.ref_docname,row.partner)

				available_qty = row.qty
				if not available_qty: continue				
				allotted_qty = required_qty if available_qty >= required_qty else available_qty
				required_qty -= allotted_qty
				row.qty -= allotted_qty
				if row.due_date <= due_date:
					d.allotted_po_qty += allotted_qty
				else:					
					d.future_po_qty += allotted_qty
				if required_qty <= 0: break						
			if required_qty <= 0:
				if d.future_po_qty:
					d.fulfill_date = row.due_date
					if can_fulfill:
						fulfill_date = max(fulfill_date, row.due_date) if fulfill_date else row.due_date
			else:
				can_fulfill,fulfill_date = False,None
		
		d.allotted_qty = d.allotted_stock_qty + d.allotted_po_qty
		d.short_qty = (d.qty - d.allotted_qty) if (d.qty - d.allotted_qty) else 0
		if d.qty > d.allotted_qty:
			if d.allotted_qty:
				partial_available = True
			else:
				not_available = True
			d.output_qty = d.allotted_qty / (d.qty_per or 1)
		else:
			d.output_qty = d.open_qty
		min_output_qty = min(min_output_qty,d.output_qty)
		if not only_actual_stock and d.arrival_date:
			last_arrival_date = last_arrival_date and max(last_arrival_date, d.arrival_date) or d.arrival_date
		if idx >= last_idx or work_order != data[idx+1].ref_docname:
			if not_available:
				data[cur_work_order_idx]['availability_status'] = _('No Material Available')
			elif partial_available:
				data[cur_work_order_idx]['availability_status'] = _('Partial Available')

			if not only_actual_stock and partial_available and fully_availability_priority:
				data[cur_work_order_idx]['availability_status'] = _('Bypassed Partial Available')
				data[cur_work_order_idx]['available_percent'] = 0
				item_po_qty_map = original_item_po_qty_map
				item_stock_qty_map = original_item_stock_qty_map
				for j in range(cur_work_order_idx,idx+1):
					for field in ('allotted_qty', 'allotted_stock_qty', 'available_percent',
						'allotted_po_qty', 'future_po_qty','short_qty'):
						data[j][field] = 0
			else:
				data[cur_work_order_idx]['available_qty'] = min_output_qty
				data[cur_work_order_idx]['available_percent'] = min_output_qty * 100 / data[cur_work_order_idx]['open_qty'] 
				data[cur_work_order_idx]['reschedule_date'] = fulfill_date

	if filters.get('only_short_qty_item'):
		data = [d for d in data if d.qty > d.allotted_qty]

	current_order = ''
	for d in data:
		if d.get('ref_docname') != current_order:
			current_order = d.get('ref_docname')
			d['first_order_line'] = True
		#elif not filters.get('hide_order_lines'):
		#	for f in header_fields:
		#		d[f] = ''

	if filters.get('hide_order_lines'):
		data = [d for d in data if d.first_order_line]			

	return data

def get_columns(filters = {}):
	columns = [
		{
			"label": _("Ref DocType"),
			"fieldname": "ref_doctype",
			"fieldtype": "Data",
			"hidden": 1
		},
		{
			"label": _("Work Order"),
			"fieldname": "ref_docname",
			"fieldtype": "Dynamic Link",
			"options": "ref_doctype",
			"width": 160,
		},
		{
			"label": _("Status"),
			"fieldname": "status",
			"fieldtype": "Data",
			"width": 60,
		},
		{
			"label": _("Item Code"),
			"fieldname": "parent_item_code",
			"fieldtype": "Link",
			"options": "Item",
			"width": 120,
		},
		{
			"label": _("Item Name"),
			"fieldname": "parent_item_name",
			"fieldtype": "Data",
			"width": 130,
		},
		{
			"label": _("Warehouse"),
			"options": "Warehouse",
			"fieldname": "fg_warehouse",
			"fieldtype": "Link",
			"width": 100,
		},
		{"label": _("Order Qty"), "fieldname": "order_qty", "fieldtype": "Float", "width": 100},
		{"label": _("Produced Qty"), "fieldname": "produced_qty", "fieldtype": "Float", "width": 100},
		{"label": _("Open Qty"), "fieldname": "open_qty", "fieldtype": "Float", "width": 100},
		{"label": _("Available Produce Qty"), "fieldname": "available_qty", "fieldtype": "Float", "width": 100},
		{"label": _("Available %"), "fieldname": "available_percent", "fieldtype": "Float","precision":2, "width": 70},
		{
			"label": _('Planned Start Date'),
			"fieldname": 'due_date',
			"fieldtype": 'Date',
			"width": 110,
		},
				{
			"label": _("Availability Status"),
			"fieldname": "availability_status",
			"width": 80,
		}
		]
	
	if not filters.get('only_actual_stock'):
		columns.append({
			"label": _("Order Reschedule Date"),
			"fieldname": "reschedule_date",
			"fieldtype": "Date",
			"width": 140,
		})

	if not filters.get('hide_order_lines'):
		columns.extend([
			{
			"label": _("Raw Material Code"),
			"fieldname": "item_code",
			"fieldtype": "Link",
			"options": "Item",
			"width": 120,
			},
			{
				"label": _("Raw Material Name"),
				"fieldname": "item_name",
				"fieldtype": "Data",
				"width": 130,
			},
			{
				"label": _("Warehouse"),
				"options": "Warehouse",
				"fieldname": "warehouse",
				"fieldtype": "Link",
				"width": 110,
			},
			{"label": _("Required Qty"), "fieldname": "qty", "fieldtype": "Float", "width": 100},
			{"label": _("Allotted Qty"), "fieldname": "allotted_qty", "fieldtype": "Float", "width": 100},
			{"label": _("Available Stock Qty"), "fieldname": "available_stock_qty", "fieldtype": "Float", "width": 110},
			{"label": _("Allotted Stock Qty"), "fieldname": "allotted_stock_qty", "fieldtype": "Float", "width": 100}
		])

	if not filters.get('only_actual_stock'):
		columns.extend([	
			{"label": _("Allotted PO Qty"), "fieldname": "allotted_po_qty", "fieldtype": "Float", "width": 100},
			{"label": _("Future PO Qty"), "fieldname": "future_po_qty", "fieldtype": "Float", "width": 100},
			{"label": _("Available Produce Qty"), "fieldname": "output_qty", "fieldtype": "Float", "width": 100}
		])

	columns.append({"label": _("Short Qty"), "fieldname": "short_qty", "fieldtype": "Float", "width": 100})

	if not filters.get('only_actual_stock'):
		columns.extend([	
			{
				"label": _("Earliest Arrival Date"),
				"fieldname": "arrival_date",
				"fieldtype": "Date",
				"width": 110,
			},
			{
				"label": _("Earliest Fulfill Date"),
				"fieldname": "fulfill_date",
				"fieldtype": "Date",
				"width": 100,
			},
			{
				"label": _("Earliest GR Qty"),
				"fieldname": "arrival_qty",
				"fieldtype": "Float",
				"width": 110,
			},
			{
				"label": _("Purchase Order"),
				"fieldname": "purchase_order",
				"fieldtype": "Link",
				"options":"Purchase Order",
				"width": 130,
			},
			{
				"label": _("Supplier"),
				"fieldname": "supplier",
				"fieldtype": "Link",
				"options":"Supplier",
				"width": 120,
			}
		]
		)
	return columns

@frappe.whitelist()
def reschedule_work_order(data):
	data = json.loads(data)
	ok_count = 0
	error_msg = ''
	for row in data:
		reschedule_date = row.get('reschedule_date')
		try:
			if row.get("ref_doctype") == "Work Order":
				frappe.db.set_value('Work Order', row.get('ref_docname'), 'planned_start_date', reschedule_date)
				ok_count += 1
		except Exception as e:
			error_msg = str(e)
	frappe.db.commit()
	msg = _("Updated {0} work orders").format(ok_count)
	if error_msg:
		msg += " Update with errors:" + error_msg	
	return 	msg


"""
from zelin_am.agile_manufacturing.report.work_order_material_availability.work_order_material_availability import *
filters = frappe._dict({
	'company':'frappedemo',
	'fully_availability_priority':0,
	'only_actual_stock':1
})
company = filters.get('company')
fully_availability_priority = filters.get('fully_availability_priority')
only_actual_stock = filters.get('only_actual_stock')
warehouses =  frappe.get_all('Warehouse', filters={'company': company, 'mrp_exclude':0}, pluck='name')
data = get_open_wo_gi(warehouses, filters = filters, need_header_info = True)
wip_warehouse = {row.wip_warehouse for row in data}
def get_warehouses(warehouses, wip_warehouse):
	return [w for w in warehouses if w not in wip_warehouse]
warehouses = get_warehouses(warehouses,wip_warehouse)
item_stock_qty_map = get_item_code_onhand_map(company,warehouses)
c, d = execute({'company':'frappedemo'})
"""